package com.piece.core.message.client.impl;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.piece.core.framework.exception.ErrorCode;
import com.piece.core.framework.support.bootstrap.DataTable;
import com.piece.core.framework.support.convert.Convert;
import com.piece.core.framework.support.response.AjaxResponse;
import com.piece.core.framework.util.net.URLEncoder;
import com.piece.core.framework.util.object.ClassUtil;
import com.piece.core.framework.util.string.JsonUtil;
import com.piece.core.framework.util.time.DateUtil;
import com.piece.core.log.client.LogFactory;
import com.piece.core.message.client.dto.SmsReceiveRespDTO;
import com.piece.core.message.client.dto.SmsSendRespDTO;
import com.piece.core.message.client.dto.SmsTemplateRespDTO;
import com.piece.core.message.constant.SmsErrorCodeConstants;
import com.piece.core.message.enums.AuditStatusType;
import com.piece.core.message.properties.SmsProperties;
import com.yunpian.sdk.YunpianClient;
import com.yunpian.sdk.constant.YunpianConstant;
import com.yunpian.sdk.model.Result;
import com.yunpian.sdk.model.Template;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.map.LinkedMap;
import org.springframework.util.Assert;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.yunpian.sdk.constant.Code.*;
import static com.yunpian.sdk.constant.Code.IP_NOT_ALLOWED;

/**
 * 云片短信客户端的实现
 */
@Slf4j
public class YunpianSmsClient extends AbstractSmsClient {

    /**
     * 云信短信客户端
     */
    private volatile YunpianClient client;

    public YunpianSmsClient(String applicationName, SmsProperties properties, LogFactory auditLogFactory) {
        super(applicationName, properties, auditLogFactory);
        Assert.notEmpty(new Object[]{ properties.getApiKey() }, "apiKey 不能为空");
    }

    @Override
    public void doInit() {
        YunpianClient oldClient = client;
        // 初始化新的客户端
        YunpianClient newClient = new YunpianClient(properties.getApiKey());
        newClient.init();
        this.client = newClient;
        // 销毁老的客户端
        if (null != oldClient) {
            oldClient.close();
        }
    }

    @Override
    protected AjaxResponse doSendSms(String sendLogId, String mobile, String apiTemplateId, LinkedMap templateParams) throws Throwable {
        return invoke(() -> {
            Map<String, String> request = new HashMap<>();
            request.put(YunpianConstant.MOBILE, mobile);
            request.put(YunpianConstant.TPL_ID, apiTemplateId);
            request.put(YunpianConstant.TPL_VALUE, formatTplValue(templateParams));
            request.put(YunpianConstant.UID, String.valueOf(sendLogId));
            request.put(YunpianConstant.CALLBACK_URL, properties.getCallbackUrl());
            return client.sms().tpl_single_send(request);
        }, response -> SmsSendRespDTO.builder()
                .bizId(String.valueOf(response.getSid()))
                .outId(sendLogId)
                .content(formatTplValue(templateParams))
                .receiveDate(DateUtil.formatDate(new Date(), DateUtil.FORMAT_YYYY_MM_DD_HH_MM_SS))
                .build());
    }

    /**
     * @See <a href="https://www.yunpian.com/official/document/sms/zh_cn/introduction_demos_encode_sample"/>
     */
    private static String formatTplValue(LinkedMap templateParams) {
        if (ClassUtil.isEmpty(templateParams)) {
            return "";
        }

        StringJoiner joiner = new StringJoiner("&");
        templateParams.forEach((key, value) -> joiner.add(String.format("#%s#=%s", key,
                URLEncoder.DEFAULT.encode(String.valueOf(value), StandardCharsets.UTF_8))));
        return joiner.toString();
    }

    @Override
    protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) {
        List<SmsReceiveStatus> statuses = JsonUtil.toList(text, SmsReceiveStatus.class);
        return statuses.stream().map(status -> {
            SmsReceiveRespDTO data = SmsReceiveRespDTO.builder()
                    .outId(Convert.toStr(status.getUid()))
                    .errorCode(status.getErrorMsg())
                    .errorMsg(status.getErrorDetail())
                    .receiveTime(status.getUserReceiveTime())
                    .success(Objects.equals(status.getReportStatus(), "SUCCESS"))
                    .mobile(status.getMobile())
                    .bizId(String.valueOf(status.getSid()))
                    .build();
            return data;
        }).collect(Collectors.toList());
    }

    @Override
    protected AjaxResponse doGetSmsTemplate(String apiTemplateId) throws Throwable {
        return invoke(() -> {
            Map<String, String> request = new HashMap<>();
            request.put(YunpianConstant.APIKEY, properties.getApiKey());
            request.put(YunpianConstant.TPL_ID, apiTemplateId);
            return client.tpl().get(request);
        }, response -> {
            Template template = response.get(0);
            return SmsTemplateRespDTO.builder()
                    .id(String.valueOf(template.getTpl_id()))
                    .content(template.getTpl_content())
                    .auditStatus(convertSmsTemplateAuditStatus(template.getCheck_status()))
                    .auditReason(template.getReason())
                    .build();
        });
    }

    Integer convertSmsTemplateAuditStatus(String checkStatus) {
        switch (checkStatus) {
            case "CHECKING": return AuditStatusType.CHECKING.getStatus();
            case "SUCCESS": return AuditStatusType.SUCCESS.getStatus();
            case "FAIL": return AuditStatusType.FAIL.getStatus();
            default: throw new IllegalArgumentException(String.format("未知审核状态(%s)", checkStatus));
        }
    }

    @Override
    public AjaxResponse queryDetail(String mobile, String bizId, int pageIndex, int pageSize) {
        DataTable dataTable = new DataTable();
        dataTable.setStart(pageIndex);
        dataTable.setLength(pageSize);
        dataTable.setRecordsTotal(0);
        dataTable.setData(Collections.EMPTY_LIST);
        return AjaxResponse.result(ErrorCode.SUCCESS, dataTable);
    }

    <T, R> AjaxResponse invoke(Supplier<Result<T>> requestConsumer, Function<T, R> responseConsumer) throws Throwable {
        // 执行请求
        Result<T> result = requestConsumer.get();
        if (null != result.getThrowable()) {
            throw result.getThrowable();
        }
        // 解析结果
        R data = null;
        if (null != result.getData()) {
            data = responseConsumer.apply(result.getData());
        }
        // 拼接结果
        return AjaxResponse.result(convert(result.getCode(), result.getMsg()), data);
    }

    private ErrorCode convert(int code, String msg) {
        switch (code) {
            case OK:
                return ErrorCode.SUCCESS;
            case ARGUMENT_MISSING:
                return SmsErrorCodeConstants.SMS_API_PARAM_ERROR;
            case BAD_ARGUMENT_FORMAT:
                return SmsErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR;
            case TPL_NOT_FOUND:
            case TPL_NOT_VALID:
                return SmsErrorCodeConstants.SMS_TEMPLATE_INVALID;
            case MONEY_NOT_ENOUGH:
                return SmsErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH;
            case BLACK_WORD:
                return SmsErrorCodeConstants.SMS_SEND_CONTENT_INVALID;
            case DUP_IN_SHORT_TIME:
            case TOO_MANY_TIME_IN_5:
            case DAY_LIMIT_PER_MOBILE:
            case HOUR_LIMIT_PER_MOBILE:
                return SmsErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL;
            case BLACK_PHONE_FILTER:
                return SmsErrorCodeConstants.SMS_MOBILE_BLACK;
            case SIGN_NOT_MATCH:
            case BAD_SIGN_FORMAT:
            case SIGN_NOT_VALID:
                return SmsErrorCodeConstants.SMS_SIGN_INVALID;
            case BAD_API_KEY:
                return SmsErrorCodeConstants.SMS_ACCOUNT_INVALID;
            case API_NOT_ALLOWED:
                return SmsErrorCodeConstants.SMS_PERMISSION_DENY;
            case IP_NOT_ALLOWED:
                return SmsErrorCodeConstants.SMS_IP_DENY;
            default:
                break;
        }

        return new ErrorCode(500, msg);
    }

    /**
     * 短信接收状态
     *
     * 参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档
     */
    @Data
    public static class SmsReceiveStatus {
        /**
         * 接收状态
         */
        @JsonProperty("report_status")
        private String reportStatus;
        /**
         * 接收手机号
         */
        private String mobile;
        /**
         * 运营商返回的代码
         */
        @JsonProperty("error_msg")
        private String errorMsg;
        /**
         * 运营商反馈代码的中文解释
         */
        @JsonProperty("error_detail")
        private String errorDetail;
        /**
         * 短信编号
         */
        private String sid;
        /**
         * 用户自定义 log_id
         */
        private String uid;
        /**
         * 用户接收时间
         */
        @JsonProperty("user_receive_time")
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date userReceiveTime;
    }
}